package com.example.spring.jpa.specification;


import com.example.spring.jpa.ast.MinorOperation;
import com.example.spring.jpa.ast.PriorityOperation;
import com.example.spring.jpa.utils.ClassUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.util.*;

/**
 * @author: orange
 * @create: 2023-07-23 17:58
 * @description:
 */
public class OperationOrStaticHandle {
    private Root<?> root;
    private CriteriaBuilder builder;
    private CriteriaQuery<?> query;
    private final Map<String, Object> fieldMap;
    private final StackParse stackParse = new StackParse();
    private final BasicTypeFacade generateFacade = new BasicTypeFacade();
    private final GenerateApplicationContext generateApplicationContext = new GenerateApplicationContext();
    private final PriorityOperation[] priorityOperations = PriorityOperation.values();
    private final MinorOperation[] minorOperations = MinorOperation.values();
    private final ClassUtils classUtils = new ClassUtils();

    public OperationOrStaticHandle(Class<?> aClass) {
        this.fieldMap = classUtils.allFieldAndFieldSuper(aClass);
        generateApplicationContext.setObjectMap(fieldMap);
    }

    public void closeBracket() {
        String op;
        while (!(op = stackParse.minorPop()).equals("(")) {
            generatePredicate(op);
        }
    }

    public void openBracket() {
        stackParse.minorPush("(");
    }

    public void staticExpHandle(String str) throws ParseException {
        stackParse.staticExpPush(str);
        while (!stackParse.priorityIsEmpty()) {
            String operation = stackParse.priorityPop();
            String right = stackParse.staticExpPop();
            String left  = stackParse.staticExpPop();
            Field field = (Field) fieldMap.get(left);
            PredicateGenerate predicateGenerate
                    = generateFacade.predicateFormTypeName(field.getType().getTypeName());
            if (predicateGenerate != null) {
                generateApplicationContext.setOperation(operation);
                generateApplicationContext.setLeft(left);
                generateApplicationContext.setRight(right);
                stackParse.predicatePush(predicateGenerate.generate(generateApplicationContext));
            } else {
                throw new RuntimeException(String.format("暂时不支持%s运算符！", operation));
            }
        }
    }

    // 左右两边的表达式，
    public void dichotomyHandle(String operation) throws ParseException {
        if (isMinor(operation)) {
            stackParse.minorPush(operation);
        } else {
            stackParse.priorityPush(operation);
        }
    }

    // 一半表达式，左边是变量。
    public void noDichotomyHandle(String operation) throws ParseException {
        String left = stackParse.staticExpPop();
        PredicateGenerate predicateGenerate
                = generateFacade.predicateFormTypeName(fieldMap.get(left).getClass().getTypeName());
        if (predicateGenerate != null) {
            generateApplicationContext.setOperation(operation);
            generateApplicationContext.setLeft(left);
            generateApplicationContext.setRight(null);
            stackParse.predicatePush(predicateGenerate.generate(generateApplicationContext));
        } else {
            throw new RuntimeException(String.format("暂时不支持%s运算符！", operation));
        }
    }

    // ! 或 not
    public void symmetryHandle(String operation) {
        if (MinorOperation.NOT$2.getOperation().equals(operation)
                || MinorOperation.NOT$1.getOperation().equals(operation)) {
            Predicate predicate = stackParse.predicatePop();
            stackParse.predicatePush(builder.not(predicate));
        }
    }

    public void finish() throws ParseException {
//        while (!stackParse.priorityIsEmpty() && !stackParse.staticExpIsEmpty()) {
//            String operation = stackParse.priorityPop();
//            String right = stackParse.staticExpPop();
//            String left = stackParse.staticExpPop();
//
//            Field field = (Field) fieldMap.get(left);
//            PredicateGenerate predicateGenerate
//                    = generateFacade.predicateFormTypeName(field.getType().getTypeName());
//
//            generateApplicationContext.setOperation(operation);
//            generateApplicationContext.setLeft(left);
//            generateApplicationContext.setRight(right);
//
//            stackParse.predicatePush(predicateGenerate.generate(generateApplicationContext));
//        }

        while (!stackParse.minorIsEmpty() && !stackParse.predicateEmpty()) {
            generatePredicate(stackParse.minorPop());
        }
    }

    public Predicate pop() {
        return stackParse.predicatePop();
    }

    private boolean isMinor(String operation) {
        for (MinorOperation minorOperation : this.minorOperations) {
            if (minorOperation.getOperation().equals(operation)) {
                return true;
            }
        }
        return false;
    }

    private boolean isPriority(String operation) {
        for (PriorityOperation priorityOperation : this.priorityOperations) {
            if (priorityOperation.getOperation().equals(operation)) {
                return true;
            }
        }
        return false;
    }

    private void generatePredicate(String op) {
        if (MinorOperation.OR$1.getOperation().equals(op)
                || MinorOperation.OR$2.getOperation().equals(op)) {
            Predicate[] predicates = { stackParse.predicatePop(), stackParse.predicatePop() };
            stackParse.predicatePush(builder.or(predicates));
        } else if (MinorOperation.AND$1.getOperation().equals(op)
                || MinorOperation.AND$2.getOperation().equals(op)) {
            Predicate[] predicates = { stackParse.predicatePop(), stackParse.predicatePop() };
            stackParse.predicatePush(builder.and(predicates));
        } else if (MinorOperation.NOT$1.getOperation().equals(op)
                || MinorOperation.NOT$2.getOperation().equals(op)) {
            stackParse.predicatePush(builder.not(stackParse.predicatePop()));
        }
    }

    public void setRoot(Root<?> root) {
        generateApplicationContext.setRoot(root);
        this.root = root;
    }

    public void setBuilder(CriteriaBuilder builder) {
        generateApplicationContext.setBuilder(builder);
        this.builder = builder;
    }

    public void setQuery(CriteriaQuery<?> query) {
        this.query = query;
    }
}
